﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ParallelRuntimeLibrary.Internal;
using System.Threading;

namespace ParallelRuntimeLibrary.HPDispatcher
{
    // High Performance Dispatcher. Copyed and Improved for Alpha 24 source code (Haytham ElFadeel).

    public static class Parallel
    {
        
        private delegate void DoWorkDelegate(int from, int to, uint pId, Action<int> body);

        private delegate void DoWorkDelegateS(int from, int to, uint pId, Action<int, ParallelLoopState> body);

        private delegate void DoWorkDelegate<T>(int from, int to, uint pId, Action<int, T> body);

        private delegate void DoWorkDelegateS<T>(int from, int to, uint pId, Action<int, ParallelLoopState, T> body);

        /*/////////////////////////////////////////////////////////////////////////////////////////////////////////*/

        public static void For(int fromIncluded, int toExcluded, Action<int> body)
        {
            For(fromIncluded, toExcluded, ThreadPriority.Normal, body);
        }

        public static void For<T>(int fromIncluded, int toExcluded, Func<T> prepare, Action<int, T> body)
        {
            For(fromIncluded, toExcluded, ThreadPriority.Normal, prepare, body);
        }

        //

        public static void For(int fromIncluded, int toExcluded, ThreadPriority taskPriority, Action<int> body)
        {
            For(fromIncluded, toExcluded, false, taskPriority, body);
        }

        public static void For(int fromIncluded, int toExcluded, bool overloading, ThreadPriority taskPriority, Action<int> body)
        {
            // 1- Get processors number that assign for this process, and calc the work pieces
            uint PCount = (uint)NativeMethod.GetProcessorCount();
            PCount = overloading == true ? PCount + 1 : PCount;

            int All = toExcluded - fromIncluded;

            if (All == 0)
                return;
            else if (PCount == 1)
            {
                for (int i = fromIncluded; i < toExcluded; i++)
                    body(i);
            }
            else if (All <= PCount)
            {
                CountdownEvent waitHandle = new CountdownEvent(All);

                int o = 0;
                for (int i = fromIncluded; i < toExcluded; i++, o++)
                {
                    DoWorkDelegate d = new DoWorkDelegate(delegate(int F, int T, uint pID, Action<int> B)
                    {
                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), (uint)i);
                        Thread.CurrentThread.Priority = taskPriority;

                        body(i);
                        waitHandle.Signal(1);

                        Thread.CurrentThread.Priority = ThreadPriority.Normal;

                    });

                    d.BeginInvoke(i, i, (uint)(i), body, null, null);
                }

                waitHandle.Wait();
            }
            else
            {
                int p = All / (int)PCount;
                int f = fromIncluded;
                int t = fromIncluded;

                CountdownEvent waitHandle = new CountdownEvent((int)PCount);

                for (int i = 0; i < PCount; i++)
                {
                    f = t;
                    t = (i + 1 == PCount) ? toExcluded : f + p;

                    // 3- Create a delgate that will work into different thread
                    DoWorkDelegate d = new DoWorkDelegate(delegate(int F, int T, uint pID, Action<int> B)
                    {
                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), pID);
                        Thread.CurrentThread.Priority = taskPriority;

                        for (int ii = F; ii < T; ii++)
                            body(ii);

                        waitHandle.Signal(1);

                        Thread.CurrentThread.Priority = ThreadPriority.Normal;
                    });

                    d.BeginInvoke(f, t, (uint)(i), body, null, null);
                }

                // 5- Wait until the all threads finished.
                waitHandle.Wait();
            }
        }

        public static void For<T>(int fromIncluded, int toExcluded, ThreadPriority taskPriority, Func<T> prepare, Action<int, T> body)
        {
            For<T>(fromIncluded, toExcluded, false, taskPriority, prepare, body);
        }

        public static void For<T>(int fromIncluded, int toExcluded, bool overloading, ThreadPriority taskPriority, Func<T> prepare, Action<int, T> body)
        {
            // 1- Get processors number that assign for this process, and calc the work pieces
            uint PCount = (uint)NativeMethod.GetProcessorCount();
            PCount = overloading == true ? PCount + 1 : PCount;

            int All = toExcluded - fromIncluded;

            if (All == 0)
                return;
            else if (PCount == 1)
            {
                var Ped = prepare();

                for (int i = fromIncluded; i < toExcluded; i++)
                    body(i, Ped);
            }
            else if (All <= PCount)
            {
                CountdownEvent waitHendle = new CountdownEvent(All);

                int o = 0;
                for (int i = fromIncluded; i < toExcluded; i++, o++)
                {
                    DoWorkDelegate<T> d = new DoWorkDelegate<T>(delegate(int Ff, int Tt, uint pID, Action<int, T> B)
                    {
                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), (uint)i);
                        Thread.CurrentThread.Priority = taskPriority;

                        var Ped = prepare();

                        body(i, Ped);
                        waitHendle.Signal();

                        Thread.CurrentThread.Priority = ThreadPriority.Normal;
                    });

                    d.BeginInvoke(i, i, (uint)(i), body, null, null);
                }

                waitHendle.Wait();
            }
            else
            {
                int p = All / (int)PCount;
                int f = fromIncluded;
                int t = fromIncluded;

                CountdownEvent waitHandle = new CountdownEvent((int)PCount);

                for (int i = 0; i < PCount; i++)
                {
                    f = t;
                    t = (i + 1 == PCount) ? toExcluded : f + p;

                    // 3- Create a delgate that will work into different thread
                    DoWorkDelegate<T> d = new DoWorkDelegate<T>(delegate(int Ff, int Tt, uint pID, Action<int, T> B)
                    {
                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), pID);
                        Thread.CurrentThread.Priority = taskPriority;

                        var Ped = prepare();

                        for (int ii = Ff; ii < Tt; ii++)
                            body(ii, Ped);

                        waitHandle.Signal(1);
                        Thread.CurrentThread.Priority = ThreadPriority.Normal;
                    });

                    d.BeginInvoke(f, t, (uint)(i), body, null, null);
                }

                // 5- Wait until the all threads finished.
                waitHandle.Wait();
            }
        }

        //
        
        public static void For(int fromIncluded, int toExcluded, ThreadPriority taskPriority, Action<int, ParallelLoopState> body)
        {
            For(fromIncluded, toExcluded, false, taskPriority, body);
        }

        public static void For(int fromIncluded, int toExcluded, bool overloading, ThreadPriority taskPriority, Action<int, ParallelLoopState> body)
        {
            // 1- Get processors number that assign for this process, and calc the work pieces
            uint PCount = (uint)NativeMethod.GetProcessorCount();
            PCount = overloading == true ? PCount + 1 : PCount;

            int All = toExcluded - fromIncluded;

            if (All == 0)
                return;
            else if (PCount == 1)
            {
                ParallelLoopState pls = new ParallelLoopState(new Thread[1] { Thread.CurrentThread });

                try
                {
                    for (int i = fromIncluded; i < toExcluded; i++)
                        body(i, pls);
                }
                catch (ThreadAbortException)
                {
                    return;
                }
            }
            else if (All <= PCount)
            {
                CountdownEvent waitHandle = new CountdownEvent(All);
                Thread[] Dthreads = new Thread[All];
                ParallelLoopState pls = new ParallelLoopState(Dthreads);

                int o = 0;
                for (int i = fromIncluded; i < toExcluded; i++, o++)
                {
                    DoWorkDelegateS d = new DoWorkDelegateS(delegate(int F, int T, uint pID, Action<int, ParallelLoopState> B)
                    {
                        // 3.3- Get he current thread.
                        Dthreads[F] = Thread.CurrentThread;

                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), (uint)i);
                        Thread.CurrentThread.Priority = taskPriority;

                        try
                        {
                            body(F, pls);
                        }
                        catch (ThreadAbortException)
                        {
                            return;
                        }
                        finally
                        {
                            waitHandle.Signal(1);
                            Thread.CurrentThread.Priority = ThreadPriority.Normal;
                        }

                    });


                    d.BeginInvoke(i, i, (uint)(i), body, null, null);
                }

                waitHandle.Wait();
            }
            else
            {
                int p = All / (int)(PCount);
                int f = fromIncluded;
                int t = fromIncluded;

                CountdownEvent waitHandle = new CountdownEvent((int)PCount);
                Thread[] Dthreads = new Thread[PCount];
                ParallelLoopState pls = new ParallelLoopState(Dthreads);

                for (int i = 0; i < PCount; i++)
                {
                    f = t;
                    t = (i + 1 == PCount) ? toExcluded : f + p;

                    // 3- Create a delgate that will work into different thread
                    DoWorkDelegateS d = new DoWorkDelegateS(delegate(int F, int T, uint pID, Action<int, ParallelLoopState> B)
                    {
                        // 3.3- Get the current thread.
                        Dthreads[pID] = Thread.CurrentThread;

                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), pID);
                        Thread.CurrentThread.Priority = taskPriority;

                        try
                        {
                            for (int ii = F; ii < T; ii++)
                                body(ii, pls);
                        }
                        catch (ThreadAbortException)
                        {
                            return;
                        }
                        finally
                        {
                            waitHandle.Signal();
                            Thread.CurrentThread.Priority = ThreadPriority.Normal;
                        }
                    });

                    d.BeginInvoke(f, t, (uint)(i), body, null, null);
                }

                // 5- Wait until the all threads finished.
                waitHandle.Wait();
            }
        }

        public static void For<T>(int fromIncluded, int toExcluded, ThreadPriority taskPriority, Func<T> prepare, Action<int, ParallelLoopState, T> body)
        {
            For<T>(fromIncluded, toExcluded, false, taskPriority, prepare, body);
        }

        public static void For<T>(int fromIncluded, int toExcluded, bool overloading, ThreadPriority taskPriority, Func<T> prepare, Action<int, ParallelLoopState, T> body)
        {
            // 1- Get processors number that assign for this process, and calc the work pieces
            uint PCount = (uint)NativeMethod.GetProcessorCount();
            PCount = overloading == true ? PCount + 1 : PCount;

            int All = toExcluded - fromIncluded;

            if (All == 0)
                return;
            else if (PCount == 1)
            {
                ParallelLoopState pls = new ParallelLoopState(new Thread[1] { Thread.CurrentThread });
                var Ped = prepare();

                try
                {
                    for (int i = fromIncluded; i < toExcluded; i++)
                        body(i, pls, Ped);
                }
                catch (ThreadAbortException)
                {
                    return;
                }
            }
            else if (All <= PCount)
            {
                CountdownEvent waitHandle = new CountdownEvent(All);
                Thread[] Dthreads = new Thread[All];
                ParallelLoopState pls = new ParallelLoopState(Dthreads);

                int o = 0;
                for (int i = fromIncluded; i < toExcluded; i++, o++)
                {
                    DoWorkDelegateS<T> d = new DoWorkDelegateS<T>(delegate(int Ff, int Tt, uint pID, Action<int, ParallelLoopState, T> B)
                    {
                        // 3.3- Get he current thread.
                        Dthreads[Ff] = Thread.CurrentThread;

                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), (uint)i);

                        var Ped = prepare();

                        try
                        {
                            body(Ff, pls, Ped);
                        }
                        catch (ThreadAbortException)
                        {
                            return;
                        }
                        finally
                        {
                            waitHandle.Signal();
                            Thread.CurrentThread.Priority = ThreadPriority.Normal;
                        }

                    });

                    d.BeginInvoke(i, i, (uint)(i), body, null, null);
                }

                waitHandle.Wait();
            }
            else
            {
                int p = All / (int)PCount;
                int f = fromIncluded;
                int t = fromIncluded;

                CountdownEvent waitHandle = new CountdownEvent((int)PCount);
                Thread[] Dthreads = new Thread[PCount];
                ParallelLoopState pls = new ParallelLoopState(Dthreads);

                for (int i = 0; i < PCount; i++)
                {
                    f = t;
                    t = f + p;

                    // 3- Create a delgate that will work into different thread
                    DoWorkDelegateS<T> d = new DoWorkDelegateS<T>(delegate(int Ff, int Tt, uint pID, Action<int, ParallelLoopState, T> B)
                    {
                        // 3.3- Get the current thread.
                        Dthreads[pID] = Thread.CurrentThread;

                        // 4- Assign this thread to Processor. and call the body
                        NativeMethod.SetThreadIdealProcessor(NativeMethod.GetCurrentThread(), pID);
                        Thread.CurrentThread.Priority = taskPriority;

                        var Ped = prepare();

                        try
                        {
                            for (int ii = Ff; ii < Tt; ii++)
                                body(ii, pls, Ped);
                        }
                        catch (ThreadAbortException)
                        {
                            return;
                        }
                        finally
                        {
                            waitHandle.Signal();
                            Thread.CurrentThread.Priority = ThreadPriority.Normal;
                        }
                    });

                    d.BeginInvoke(f, t, (uint)(i), body, null, null);
                }

                // 5- Wait until the all threads finished.
                waitHandle.Wait();
            }
        }
    }
}
